/* ------------------------- htmlparser/element.js -------------------------- */
'use strict';
VED.htmlParser.element = new Class({
   initialize: function( name, attributes ) {
      this.name = name;
      this.attributes = attributes || {};
      this.children = [];

      // Reveal the real semantic of our internal custom tag name (#6639),
      // when resolving whether it's block like.
      var realName = name || '',
         prefixed = realName.match( /^ved:(.*)/ );
      prefixed && ( realName = prefixed[ 1 ] );

      var isBlockLike = !!( VED.dtd.$nonBodyContent[ realName ] || VED.dtd.$block[ realName ] || VED.dtd.$listItem[ realName ] || VED.dtd.$tableContent[ realName ] || VED.dtd.$nonEditable[ realName ] || realName == 'br' );

      this.isEmpty = !!VED.dtd.$empty[ name ];
      this.isUnknown = !VED.dtd[ name ];

      this._ = {
         isBlockLike: isBlockLike,
         hasInlineStarted: this.isEmpty || !isBlockLike
      };
   }
});

VED.htmlParser.cssStyle = function() {
   var styleText,
      arg = arguments[ 0 ],
      rules = {};

   styleText = arg instanceof VED.htmlParser.element ? arg.attributes.style : arg;

   // html-encoded quote might be introduced by 'font-family'
   // from MS-Word which confused the following regexp. e.g.
   //'font-family: &quot;Lucida, Console&quot;'
   // TODO reuse CSS methods from tools.
   ( styleText || '' ).replace( /&quot;/g, '"' ).replace( /\s*([^ :;]+)\s*:\s*([^;]+)\s*(?=;|$)/g, function( match, name, value ) {
      name == 'font-family' && ( value = value.replace( /["']/g, '' ) );
      rules[ name.toLowerCase() ] = value;
   });

   return {

      rules: rules,

      populate: function( obj ) {
         var style = this.toString();
         if( style ) {
            obj instanceof Element ? obj.setAttribute( 'style', style ) : obj instanceof VED.htmlParser.element ? obj.attributes.style = style : obj.style = style;
         }
      },

      toString: function() {
         var output = [];
         for ( var i in rules )
            rules[ i ] && output.push( i, ':', rules[ i ], ';' );
         return output.join( '' );
      }
   };
};

(function() {
   // Used to sort attribute entries in an array, where the first element of
   // each object is the attribute name.
   var sortAttribs = function( a, b ) {
         a = a[ 0 ];
         b = b[ 0 ];
         return a < b ? -1 : a > b ? 1 : 0;
      },
      fragProto = VED.htmlParser.fragment.prototype;

   VED.htmlParser.element.implement( new VED.htmlParser.node );

   VED.htmlParser.element.implement({

      type: 1,

      add: fragProto.add,

      clone: function() {
         return new VED.htmlParser.element( this.name, this.attributes );
      },

      filter: function( filter ) {
         var element = this,
            originalName, name;

         // Filtering if it's the root node.
         if ( !element.parent )
            filter.onRoot( element );

         while ( true ) {
            originalName = element.name;

            if ( !( name = filter.onElementName( originalName ) ) ) {
               this.remove();
               return false;
            }

            element.name = name;

            if ( !( element = filter.onElement( element ) ) ) {
               this.remove();
               return false;
            }

            // New element has been returned - replace current one
            // and process it (stop processing this and return false, what
            // means that element has been removed).
            if ( element !== this ) {
               this.replaceWith( element );
               return false;
            }

            // If name has been changed - continue loop, so in next iteration
            // filters for new name will be applied to this element.
            // If name hasn't been changed - stop.
            if ( element.name == originalName )
               break;

            // If element has been replaced with something of a
            // different type, then make the replacement filter itself.
            if ( element.type != VED.NODE_ELEMENT ) {
               this.replaceWith( element );
               return false;
            }

            // This indicate that the element has been dropped by
            // filter but not the children.
            if ( !element.name ) {
               this.replaceWithChildren();
               return false;
            }
         }

         var attributes = element.attributes,
            a, value, newAttrName;

         for ( a in attributes ) {
            newAttrName = a;
            value = attributes[ a ];

            // Loop until name isn't modified.
            // A little bit senseless, but IE would do that anyway
            // because it iterates with for-in loop even over properties
            // created during its run.
            while ( true ) {
               if ( !( newAttrName = filter.onAttributeName( a ) ) ) {
                  delete attributes[ a ];
                  break;
               } else if ( newAttrName != a ) {
                  delete attributes[ a ];
                  a = newAttrName;
                  continue;
               } else
                  break;
            }

            if ( newAttrName ) {
               if ( ( value = filter.onAttribute( element, newAttrName, value ) ) === false )
                  delete attributes[ newAttrName ];
               else
                  attributes[ newAttrName ] = value;
            }
         }

         if ( !element.isEmpty )
            this.filterChildren( filter );

         return true;
      },

      filterChildren: fragProto.filterChildren,

      writeHtml: function( writer, filter ) {
         if ( filter )
            this.filter( filter );

         var name = this.name,
            attribsArray = [],
            attributes = this.attributes,
            attrName,
            attr, i, l;

         // Open element tag.
         writer.openTag( name, attributes );

         // Copy all attributes to an array.
         for ( attrName in attributes )
            attribsArray.push( [ attrName, attributes[ attrName ] ] );

         // Sort the attributes by name.
         if ( writer.sortAttributes )
            attribsArray.sort( sortAttribs );

         // Send the attributes.
         for ( i = 0, l = attribsArray.length; i < l; i++ ) {
            attr = attribsArray[ i ];
            writer.attribute( attr[ 0 ], attr[ 1 ] );
         }

         // Close the tag.
         writer.openTagClose( name, this.isEmpty );

         this.writeChildrenHtml( writer );

         // Close the element.
         if ( !this.isEmpty )
            writer.closeTag( name );
      },

      writeChildrenHtml: fragProto.writeChildrenHtml,

      replaceWithChildren: function() {
         var children = this.children;

         for ( var i = children.length; i; )
            children[ --i ].insertAfter( this );

         this.remove();
      },

      forEach: fragProto.forEach
   });
})();




